#pragma once
#include <iostream>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstring>
#include <cstdlib>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <functional>
#include <string>
#include <sys/wait.h>
#include <signal.h>
#include "err.hpp"

using namespace std;

namespace ns_server{

    //
    static const int backlog = 32;

    using func_t = function<string(const string &)>;

    class tcp_server{
    public:
    tcp_server(func_t func, uint16_t port = 8080): _func(func), _port(port), _quit(1){

    }

    ~tcp_server(){

    }

    void init(){

        //对 SIGCHLD -- 阻塞 的信号进行忽略，推荐
        signal(SIGCHLD, SIG_IGN);
        //signal(SIGCHLD, handler); -- 用回调函数回收信号，不太推荐

        //1、创建tcp套接字
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if(_listensock<0){
            cerr << "create socket error: " << strerror(errno) << endl;
            exit(SOCKET_ERR);
        }
        cout << "create socket success: " << _listensock << endl;

        //2、绑定
        //2.1、构建栈上的sockaddr_in结构体local
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        //主机转网络，只有在绑定的时候才需要手动转化，接收和发送的时候函数内部包含了转化，不用手动转化
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;
        //2.2、将local绑定到OS内核，我们前面定义的是sockaddr_in，这里需要强转
        if(bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0){
            cerr<<"bind socket error: "<< strerror(errno) << endl;
            exit(BIND_ERR);
        }
        cout << "bind success." << endl;

        //这上面都和udp的流程一样，从此处开始不一样
        //--------------------------------------

        //3、将套接字设为监听状态
        if(listen(_listensock,backlog)<0){
            cerr<<"listen socket error: "<< strerror(errno) << endl;
            exit(LISTEN_ERR);
        }


    }

    void service(int sock, const string& clientip, const uint16_t& clientport){
        char buf[1024];
        string client = clientip + '-' + to_string(clientport);
        while (1)
        {
            //当文件读取
            ssize_t s = read(sock, buf, sizeof(buf) - 1);
            if(s>0){
                buf[s] = 0;
                //进行回调，交给上层处理
                string res = _func(buf);
                cout << client << "# " <<res << endl;
                write(sock, res.c_str(), res.size());
            }
            //客户端关闭了连接
            else if(s==0){
                close(sock);
                cout << client <<" quit, sock of this client close." << endl;
            }else{
                cerr << "read error: " << strerror(errno) << endl;
                break;
            }
        }
    }

    void start(){
        //服务器不quit了
        _quit = false;
        while (!_quit){
            //4、获取连接，accept
            //被获取上来的套接字才是真正用来给客户端提供服务的
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sock = accept(_listensock, (struct sockaddr *)&client, &len);
            //提取客户端信息
            string clientip = inet_ntoa(client.sin_addr);
            uint16_t clientport = ntohs(client.sin_port);

            //获取失败转头获取下一个连接
            if(sock<0){
                cerr << "accept error " << endl;
                sleep(1);
                continue;
            }
            cout << "connect to " << sock << " from " << _listensock << ", " << clientip << "-" << clientport << endl;

            // //5、提供服务，业务处理
            // //这样写同时只有一个用户能使用服务器 -- 进到server之后进入循环，只有当前用户退出之后才退出循环，才退出server，才能再次accept
            // //需要进行多进程版本
            // service(sock, clientip, clientport);

            pid_t id = fork();
            if(id<0){
                close(sock);
                continue;
            }else if(id == 0){
                //子进程
                //父进程的fd会被子进程继承，父子用的是同一张文件描述符表，当对fd表进行更改的时候会进行写时拷贝
                //建议子进程关闭不需要的fd
                close(_listensock);

                //fork()>0代表没fork()成功，没创建子进程，还是本进程在运行
                //fork()==0代表创建了子进程
                if(fork() > 0){
                    //本进程退出，被waitpid()回收
                    exit(0);
                }
                //走到这里，就是孙子进程在帮子进程进行service
                //而因为子进程退出了，孙子进程变成孤儿进程，由系统接管

                service(sock, clientip, clientport);
                exit(0);
            }

            //父进程
            //父进程一定要关闭不需要的fd，不关闭会导致fd泄漏
            close(sock);
            //不能使用阻塞式等待
            pid_t ret = waitpid(id, nullptr, 0);    
            //不推荐WNOHANG -- 如果没有新的客户端连接，会在accept阻塞，此时如果子进程退出就不会走到waitpid，会产生不可预知的问题
            //pid_t ret = waitpid(id, nullptr, WNOHANG);
            if(ret == id){
                cout << "wait child " << id << " success" << endl;
            }
        }
    }

    private:
        uint16_t _port;
        //监听套接字的文件描述符，用于获取与客户端的连接，不断获取连接
        int _listensock;
        //服务器是否退出，默认为1，没有启动
        bool _quit;

        func_t _func;
    };
}